package com.jerome.fs.http.server;

import com.jerome.fs.http.HttpMessageParser;
import com.jerome.fs.util.ThreadPoolFactory;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * HTTP Server for handling HTTP requests.
 *
 * @see <a href="https://tobebetterjavaer.com/socket/http.html">https://tobebetterjavaer.com/socket/http.html</a>
 * @author Jerome
 * @date 2023/8/5
 */
public class HttpServer {

    /**
     * Start an HTTP server.
     *
     * @param port        The port to listen on.
     * @param httpHandler The HTTP handler for processing requests.
     * @throws IOException If an I/O error occurs.
     */
    public static void startHttpServer(int port, HttpHandler httpHandler) throws IOException {
        // Start the server
        new Thread(new ServerThread(new ServerSocket(port), httpHandler, 2)).start();
    }

    // Main task class for the HTTP server
    private static class ServerThread implements Runnable {
        // Store the ServerSocket instance passed to the constructor
        private ServerSocket serverSocket;
        // HTTP logic handler
        private HttpHandler httpHandler;
        // Create a thread pool for handling incoming HTTP requests
        // Initialize the thread pool with the specified core thread count, queue size, and rejection policy
        private ExecutorService taskExecutor;

        /**
         * Constructor for ServerThread.
         *
         * @param s           ServerSocket
         * @param httpHandler Custom HTTP handler
         * @param nThreads    Core processing thread count
         */
        public ServerThread(ServerSocket s, HttpHandler httpHandler, int nThreads) {
            this.serverSocket = s;
            this.httpHandler = httpHandler;
            this.taskExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(100000),
                    new ThreadPoolFactory("HttpServer"),
                    new ThreadPoolExecutor.AbortPolicy());

        }

        // Main task method
        @Override
        public void run() {
            // Loop to continuously accept incoming connections
            Socket socket;
            HttpTask eventTask;
            while (true) {
                socket = null;
                try {
                    // Wait for a client connection
                    socket = this.serverSocket.accept();
                    // Create an HttpTask instance, passing the Socket as a parameter
                    eventTask = new HttpTask(socket, this.httpHandler);
                    // Submit the HttpTask to the taskExecutor for execution
                    this.taskExecutor.submit(eventTask);
                } catch (RejectedExecutionException e) {
                    if (socket != null) {
                        try {
                            new PrintWriter(socket.getOutputStream()).print(HttpMessageParser.buildHttpResponse(HttpMessageParser.buildErrorResponse(e)));
                            socket.close();
                        } catch (IOException ioException) {
                            ioException.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    try {
                        // If an exception occurs, wait for 1 second before attempting again
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    }
}
